package com.xingchi.tornado.storage.core.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xingchi.tornado.basic.ErrorCode;
import com.xingchi.tornado.exception.BizException;
import com.xingchi.tornado.mybatisplus.service.AbstractBasePlusService;
import com.xingchi.tornado.security.context.RequestContext;
import com.xingchi.tornado.storage.core.FileStorageService;
import com.xingchi.tornado.storage.core.mapper.FileChunkMapper;
import com.xingchi.tornado.storage.core.model.domain.FileChunkDO;
import com.xingchi.tornado.storage.core.model.dto.FileChunkCheckDTO;
import com.xingchi.tornado.storage.core.model.dto.FileChunkDTO;
import com.xingchi.tornado.storage.core.model.dto.FileChunkMergeDTO;
import com.xingchi.tornado.storage.core.model.dto.FileChunkQueryDTO;
import com.xingchi.tornado.storage.core.model.dto.FileChunkUploadDTO;
import com.xingchi.tornado.storage.core.model.dto.FileDTO;
import com.xingchi.tornado.storage.core.model.dto.FileUserRelDTO;
import com.xingchi.tornado.storage.core.model.vo.FileChunkUploadVO;
import com.xingchi.tornado.storage.core.service.FileChunkService;
import com.xingchi.tornado.storage.core.service.FileService;
import com.xingchi.tornado.storage.core.service.FileUserRelService;
import com.xingchi.tornado.storage.core.utils.FileTypeChecker;
import com.xingchi.tornado.utils.StreamUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author xingchi
 * @date 2025-01-11 15:15
 */
@Service
public class FileChunkServiceImpl extends AbstractBasePlusService<FileChunkMapper, FileChunkDTO, FileChunkQueryDTO, FileChunkDO> implements FileChunkService {

    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private FileService fileService;
    @Autowired
    private FileUserRelService fileUserRelService;

    @Override
    public Wrapper<FileChunkDO> getWrapper(FileChunkQueryDTO query) {
        return Wrappers.lambdaQuery(FileChunkDO.class)
                .eq(Objects.nonNull(query.getChunkNumber()), FileChunkDO::getChunk, query.getChunkNumber())
                .eq(StringUtils.isNotBlank(query.getSha256()), FileChunkDO::getSha256, query.getSha256());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public FileChunkUploadVO uploadChunk(FileChunkUploadDTO fileChunkUpload) {

        // 判断文件分片是否上传过，上传过则直接返回对应的分片信息
        boolean exists = this.exists(FileChunkQueryDTO.builder()
                .chunkNumber(fileChunkUpload.getChunkNumber())
                .sha256(fileChunkUpload.getSha256()).build());
        if (!exists) {
            // 不存在
            // 获取分片上传路径
            String chunkPath = fileStorageService.uploadChunk(fileChunkUpload);

            // 存储文件分片信息
            FileChunkDTO fileChunkDTO = FileChunkDTO.builder()
                    .chunk(fileChunkUpload.getChunkNumber())
                    .fileName(fileChunkUpload.getFilename())
                    .totalChunks(fileChunkUpload.getTotalChunks())
                    .chunkPath(chunkPath)
                    .sha256(fileChunkUpload.getSha256())
                    .size(fileChunkUpload.getCurrentChunkSize())
                    .uid(fileChunkUpload.getSha256())
                    .chunkSize(fileChunkUpload.getChunkSize())
                    .build();
            this.insert(fileChunkDTO);
        }

        // 判断分片文件是否存在
        FileChunkCheckDTO fileChunkCheckDTO = FileChunkCheckDTO.builder()
                .sha256(fileChunkUpload.getSha256())
                .chunkNumber(fileChunkUpload.getChunkNumber())
                .totalChunks(fileChunkUpload.getTotalChunks())
                .build();

        return checkChunk(fileChunkCheckDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public FileUserRelDTO mergeChunks(FileChunkMergeDTO info) {

        // 合并文件分片信息
        String path = fileStorageService.mergeChunks(info, () -> {
            FileChunkCheckDTO fileChunkCheckDTO = FileChunkCheckDTO.builder()
                    .sha256(info.getSha256())
                    .totalChunks(info.getTotalChunks())
                    .chunkNumber(info.getChunkNumber())
                    .build();
            return checkChunk(fileChunkCheckDTO);
        });

        // 合并文件后将信息存储在数据中
        FileDTO fileDTO = fileService.selectBySha256(info.getSha256());
        BizException.throwIf(fileDTO != null, ErrorCode.FILE_NOT_FOUND);

        // 正常就执行插入
        String filename = info.getFilename();
        fileDTO = FileDTO.builder()
                .name(filename)
                .path(path)
                .size(info.getTotalSize())
                .suffix(FilenameUtils.getExtension(filename))
                .type(FileTypeChecker.getFileType(filename).getCode())
                .sha256(info.getSha256())
                .build();
        fileService.insert(fileDTO);

        // 插入用户文件关联信息
        FileUserRelDTO fileUserRelDTO = FileUserRelDTO.builder()
                .fileId(fileDTO.getId())
                .userId(Objects.nonNull(RequestContext.getUserId()) ? RequestContext.getUserId() : -1L)
                .name(fileDTO.getName())
                .path(fileDTO.getPath())
                .size(fileDTO.getSize())
                .suffix(fileDTO.getSuffix())
                .type(fileDTO.getType())
                .sha256(fileDTO.getSha256())
                .fileId(fileDTO.getId())
                .build();

        fileUserRelService.insert(fileUserRelDTO);
        return fileUserRelDTO;
    }

    @Override
    public FileChunkUploadVO checkChunk(FileChunkCheckDTO info) {

        Integer totalChunks = info.getTotalChunks();
        String sha256 = info.getSha256();
        Integer chunkNumber = info.getChunkNumber();

        String chunkPath = fileStorageService.getChunkPath(sha256);
        Set<Integer> uploaded = new HashSet<>();

        // 检查已上传的分片
        List<FileChunkDTO> fileChunkList = Optional.ofNullable(this.selectByQuery(FileChunkQueryDTO.builder().isPagination(false).sha256(sha256).build())).orElse(new ArrayList<>());
        Map<String, FileChunkDTO> chunkPathMappings = StreamUtils.toMap(fileChunkList, FileChunkDTO::getChunkPath, Function.identity());
        for (int i = 1; i <= totalChunks; i++) {
            String chunk = chunkPath + i + ".chunk";
            if (chunkPathMappings.containsKey(chunk)) {
                uploaded.add(i);
            }
        }

        boolean isFinished = uploaded.size() == info.getTotalChunks();

        return FileChunkUploadVO.builder()
                .skipUpload(uploaded.contains(chunkNumber))
                .uploaded(uploaded)
                .isFinished(isFinished)
                .build();
    }
}
